
#pragma once

#include <cstdint>
#include <type_traits>

#include "Containers/SharedObjectPtr.h"

struct SObjectHead;
class RObject;

struct SObjectWeakReferenceCounter
{
	int32_t WeakRefCount=0;

	//when object is destroyed, this pointer will be set to nullptr
	SObjectHead* ObjectHead=nullptr; 
};

namespace WH
{
    WHENGINE_API SObjectWeakReferenceCounter* GetOrCreateObjectWeakReferenceCounter(const RObject* InObject);

    WHENGINE_API void DeleteObjectWeakReferenceCounter(SObjectWeakReferenceCounter* InWeakRefCounter);
};


template <typename ObjectType>
class  TWeakObjectPtr
{
public:
    ObjectType* Object=nullptr;
    SObjectWeakReferenceCounter* ObjectWeakReferenceCounter=nullptr;

    TWeakObjectPtr()
    {

    };
    TWeakObjectPtr(std::nullptr_t )
    {

    };

    TWeakObjectPtr(ObjectType* InObject)
    {
        if(!InObject)
        {
            return;
        }

        static_assert(std::is_base_of_v<RObject,ObjectType>&&"Only accept RObject");
        
        Object=InObject;
        ObjectWeakReferenceCounter =WH::GetOrCreateObjectWeakReferenceCounter((RObject*)Object);
        ObjectWeakReferenceCounter->WeakRefCount++;
    };

    TWeakObjectPtr(const TWeakObjectPtr& Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        Object=Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;
        ObjectWeakReferenceCounter->WeakRefCount++;
    }

    TWeakObjectPtr(TWeakObjectPtr&& Other)
    {
        Object=Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;
        Other.Object=nullptr;
        Other.ObjectWeakReferenceCounter=nullptr;
    }

    template<typename AnotherType>
    TWeakObjectPtr(const TWeakObjectPtr<AnotherType>& Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        assert((Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass())));

        Object=(ObjectType*)Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;
        ObjectWeakReferenceCounter->WeakRefCount++;
    }

    template<typename AnotherType>
    TWeakObjectPtr(TWeakObjectPtr<AnotherType>&& Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        assert((Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass())));

        Object=Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;

        Other.Object=nullptr;
        Other.ObjectWeakReferenceCounter=nullptr;
    }


    TWeakObjectPtr(const TSharedObjectPtr< ObjectType > & Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        Object= Other.Object;
        ObjectWeakReferenceCounter= WH::GetOrCreateObjectWeakReferenceCounter((RObject*)Object);
        ObjectWeakReferenceCounter->WeakRefCount++;
    }

    TWeakObjectPtr(TSharedObjectPtr< ObjectType > && Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        Object= Other.Object;
        ObjectWeakReferenceCounter= WH::GetOrCreateObjectWeakReferenceCounter((RObject*)Object);
        ObjectWeakReferenceCounter->WeakRefCount++;

        Other.Clear();
    }

    operator TSharedObjectPtr< ObjectType >() const
    {
        return Lock();
    }

    template <typename AnyType>
    operator TSharedObjectPtr< AnyType >() const
    {
        return Lock();
    }

    TSharedObjectPtr< ObjectType > Lock() const
    {
        if(!IsValid())
        {
            return TSharedObjectPtr< ObjectType >();
        }
        return TSharedObjectPtr<ObjectType> (Object);
    }

    ~TWeakObjectPtr()
    {
        Clear();
    }

    void Clear()
    {
        if(ObjectWeakReferenceCounter)
        {
            ObjectWeakReferenceCounter->WeakRefCount--;
            if(ObjectWeakReferenceCounter->WeakRefCount==0)
            {
                WH::DeleteObjectWeakReferenceCounter(ObjectWeakReferenceCounter);
            }
        }
        Object=nullptr;
        ObjectWeakReferenceCounter=nullptr;
    }


    template<typename AnotherType>
    TWeakObjectPtr(const TSharedObjectPtr<AnotherType>& Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        assert((Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass())));

        Object=(ObjectType*)Other.Object;
        ObjectWeakReferenceCounter=WH::GetOrCreateObjectWeakReferenceCounter((RObject*)Object);
        ObjectWeakReferenceCounter->WeakRefCount++;
    }

    template<typename AnotherType>
    TWeakObjectPtr(TSharedObjectPtr<AnotherType>&& Other)
    {
        if(!Other.IsValid())
        {
            return;
        }

        assert((Other.Get()->GetClass()->CanObjectConvertTo(ObjectType::StaticClass())));

        Object=Other.Object;
        ObjectWeakReferenceCounter=WH::GetOrCreateObjectWeakReferenceCounter((RObject*)Object);
        ObjectWeakReferenceCounter->WeakRefCount++;

        Other.Clear();
    }

    TWeakObjectPtr& operator =(const TWeakObjectPtr& Other)
    {
        if(this==&Other)
        {
            return *this;
        }

        Clear();

        Object=(ObjectType*)Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;

        if(ObjectWeakReferenceCounter)
        {
            ObjectWeakReferenceCounter->WeakRefCount++;
        }

        return *this;
    }
    TWeakObjectPtr& operator =(TWeakObjectPtr&& Other)
    {
        if(this==&Other)
        {
            return *this;
        }

        Clear();

        Object=Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;
        Other.Object=nullptr;
        Other.ObjectWeakReferenceCounter=nullptr;
        return *this;
    }

    template<typename AnotherType>
    TWeakObjectPtr& operator =(const TWeakObjectPtr<AnotherType>& Other)
    {
        if((void*)this==(void*)&Other)
        {
            return *this;
        }

        Clear();

        Object=Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;
        if(ObjectWeakReferenceCounter)
        {
            ObjectWeakReferenceCounter->WeakRefCount++;
        }
        return *this;
    }

    template<typename AnotherType>
    TWeakObjectPtr& operator =(TWeakObjectPtr<AnotherType>&& Other)
    {
        if((void*)this==(void*)&Other)
        {
            return *this;
        }

        Clear();

        Object=Other.Object;
        ObjectWeakReferenceCounter=Other.ObjectWeakReferenceCounter;
        Other.Object=nullptr;
        Other.ObjectWeakReferenceCounter=nullptr;
        return *this;
    }

    TWeakObjectPtr& operator =(std::nullptr_t & )
    {
        Clear();
        return *this;
    }

    TWeakObjectPtr& operator =(ObjectType* InObject)
    {
        if(Object==InObject)
        {
            return *this;
        }

        Clear();

        if(!InObject)
        {
            return *this;
        }

        Object=InObject;
        ObjectWeakReferenceCounter=WH::GetOrCreateObjectWeakReferenceCounter((RObject*)Object);
        ObjectWeakReferenceCounter->WeakRefCount++;
        return *this;
    }

    template <typename AnyType>
    bool operator ==(const TWeakObjectPtr<AnyType>& Other) const
    {
        return ObjectWeakReferenceCounter==Other.ObjectWeakReferenceCounter;
    }


    template <typename AnyType>
    bool operator ==(const TSharedObjectPtr<AnyType>& Other) const
    {
        return IsValid()== Other.IsValid() &&Object==Other.Object ;
    }

    template <typename AnyType>
    bool operator !=(const TWeakObjectPtr<AnyType>& Other) const
    {
        return ObjectWeakReferenceCounter!=Other.ObjectWeakReferenceCounter;
    }
    template <typename AnyType>
    bool operator !=(const TSharedObjectPtr<AnyType>& Other) const
    {
        return Object!=Other.Object ||  IsValid()!= Other.IsValid();
    }

    bool IsValid() const
    {
        return ObjectWeakReferenceCounter && ObjectWeakReferenceCounter->ObjectHead;
    }

    operator bool() const
    {
        return IsValid();
    }

    ObjectType* operator -> () const
    {
        assert(IsValid());
        return Object;
    }
    ObjectType* Get() const
    {
        if(IsValid())
        {
            return Object;
        }
        return nullptr;
    }


    //for std::map key requires
    bool operator < (const TWeakObjectPtr& Other) const
    {
        return ObjectWeakReferenceCounter < Other.ObjectWeakReferenceCounter;
    }

    uint64_t GetHashCode() const
    {
        if(!IsValid())
        {
            return 0;
        }
        return (uint64_t)Object;
    }
};

//compare shared and weak
template <typename ObjectTypeA,typename ObjectTypeB>
bool operator ==(TSharedObjectPtr<ObjectTypeA> A,TWeakObjectPtr<ObjectTypeB> B)
{
    return A==B.Lock();
}